import moment from 'moment';
import { useEffect, useMemo, useRef } from 'react';
import './index.less';
import { isInRect, roundRect } from './util';

type CheckCell = {
  dataStr: string; // 2022-01-01 格式
};

interface IProps {
  activeYear: number;
  dateRange: [moment.Moment, moment.Moment];
  checkList: CheckCell[];
  onSelect?: (i: string) => void;
  selected?: string;
}

const STYLE_CONFIG = {
  width: 30,
  weekLabelOffset: 40,
  monthLabelOffset: 40,
};

const CanvasCheckCalendar = (props: IProps) => {
  const { checkList, dateRange, activeYear, onSelect, selected = '' } = props;
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const [startDate, endDate] = dateRange;

  const mouseInfo = useRef({
    hoverTarget: '', // hover的dateStr
  });

  const displayConfig = useMemo(() => {
    if (!dateRange || !checkList) {
      return {
        cellConfig: [],
        monthLabel: [],
      };
    }
    let offset = 1;
    let calcDay = moment(startDate.toISOString());
    let startWeek = startDate.week();
    const monthLabelPosition: Record<string, number> = {}; // 月份标签标注的位置

    // 本年的前几天可能不属于本年的周计算，属于上年的52周，将其算为0周
    const startWeekFlag = startDate.isSame(
      moment(`${activeYear - 1}-12-31`),
      'week',
    );
    if (startWeekFlag) {
      startWeek = 0;
    }

    const list = []; // 每个单元的对应日期与排列位置
    while (calcDay.isSameOrBefore(endDate, 'day')) {
      const month = calcDay.month() + 1;
      const year = calcDay.year();
      if (year !== activeYear) {
        // 一个日历图最多显示一年的数据
        calcDay.add(offset, 'day');
        continue;
      }
      const day = calcDay.day();
      const date = calcDay.date();
      let week = calcDay.week();
      const copy = moment(calcDay.toISOString());

      let checked = false;

      const calcDayString = calcDay.format('YYYY-MM-DD');

      // week - startWeek即与最左侧一列相差的周数（横向列数偏移），额外补充计算周日的情形，同一周的周日在右侧一列第一个
      const sundayOffset = day === 0 ? 1 : 0;

      const weekFlag = calcDay.isSame(
        moment(`${activeYear - 1}-12-31`),
        'week',
      );
      if (weekFlag) {
        week = 0;
      }

      const monthOffset = week - startWeek + sundayOffset;

      if (date === 1) {
        monthLabelPosition[month] = monthOffset;
      }

      const checkRecord = checkList.find((i) => i.dataStr === calcDayString);
      if (checkRecord) {
        checked = true;
      }

      list.push({
        dayOffset: day,
        monthOffset,
        dateStr: calcDayString,
        moment: copy,
        divider: date === 1, // 用来标记月份的第一天单元，与上一个月份分割
        checked,
      });
      calcDay.add(offset, 'day');
    }
    return {
      cellConfig: list,
      monthLabel: monthLabelPosition,
    };
  }, [dateRange, checkList]);

  const positionList = useMemo(() => {
    const { cellConfig = [] } = displayConfig;
    return cellConfig.map((i) => ({
      x: i.monthOffset * 40,
      y: i.dayOffset * 40,
      ...i,
    }));
  }, [displayConfig]);

  useEffect(() => {
    setTimeout(() => {
      resize();
    });
  }, [checkList]);

  useEffect(() => {
    initEvents();
    return () => {
      clearEvents();
    };
  }, [checkList]);

  useEffect(() => {
    render();
  }, [selected]);

  const initEvents = () => {
    if (!canvasRef.current) {
      return;
    }
    canvasRef.current.addEventListener('mousemove', mousemove);
    canvasRef.current.addEventListener('mouseleave', mouseleave);
  };

  const clearEvents = () => {
    if (!canvasRef.current) {
      return;
    }
    canvasRef.current.removeEventListener('mousemove', mousemove);
    canvasRef.current.removeEventListener('mouseleave', mouseleave);
  };

  const mousemove = (e: MouseEvent) => {
    if (!canvasRef.current) {
      return;
    }
    let currentHover = '';
    const lastHover = mouseInfo.current.hoverTarget;

    for (const i of positionList) {
      if (
        isInRect({ x: i.x, y: i.y }, 30, 30, {
          x: e.offsetX * 2 - STYLE_CONFIG.weekLabelOffset,
          y: e.offsetY * 2 - STYLE_CONFIG.monthLabelOffset,
        })
      ) {
        currentHover = i.dateStr;
        break;
      }
    }

    if (!currentHover) {
      mouseInfo.current.hoverTarget = '';
      canvasRef.current.style.cursor = '';
    } else {
      mouseInfo.current.hoverTarget = currentHover;
      canvasRef.current.style.cursor = 'pointer';
    }
    if (lastHover !== currentHover) {
      // 切换hover效果，重新渲染
      render();
    }
  };

  const mouseleave = (e: MouseEvent) => {
    if (!canvasRef.current) {
      return;
    }
    mouseInfo.current.hoverTarget = '';
  };

  const onClick = () => {
    if (mouseInfo.current.hoverTarget) {
      onSelect?.(mouseInfo.current.hoverTarget);
      render();
      return;
    }
    onSelect?.('');
  };

  const render = () => {
    if (!canvasRef.current) {
      return;
    }
    canvasRef.current.width = canvasRef.current.width;
    const ctx = canvasRef.current.getContext('2d');
    if (!ctx) {
      return;
    }

    renderMonthLabel(ctx);
    ctx.translate(0, STYLE_CONFIG.monthLabelOffset);
    renderDayLabel(ctx);
    ctx.translate(STYLE_CONFIG.weekLabelOffset, 0);
    positionList.forEach((i) => {
      renderCell(
        ctx,
        i.x,
        i.y,
        i.checked,
        i.dateStr === selected,
        mouseInfo.current.hoverTarget === i.dateStr,
      );
      if (i.divider) {
        renderDivider(ctx, i.x, i.y);
      }
    });
    ctx.translate(
      -STYLE_CONFIG.weekLabelOffset,
      -STYLE_CONFIG.monthLabelOffset,
    );
  };

  const renderDivider = (
    ctx: CanvasRenderingContext2D,
    x: number,
    y: number,
  ) => {
    ctx.save();
    ctx.strokeStyle = '#20ef2e';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(x, y + 2);
    ctx.lineTo(x + STYLE_CONFIG.width, y + 2);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  };

  const renderCell = (
    ctx: CanvasRenderingContext2D,
    x: number,
    y: number,
    fill: boolean,
    select: boolean,
    hover: boolean,
  ) => {
    const computedStyle = getComputedStyle(document.documentElement);
    const color = computedStyle.getPropertyValue('--mainColor') || 'red';
    const activeColor =
      computedStyle.getPropertyValue('--mainActiveColor') || 'red';

    ctx.save();

    if (fill) {
      ctx.fillStyle = color;
      ctx.strokeStyle = color;
    } else {
      ctx.fillStyle = 'rgb(188, 192, 223)';
      ctx.strokeStyle = 'rgb(188, 192, 223)';
    }
    if (select) {
      ctx.strokeStyle = 'blue';
      ctx.lineWidth = 4;
      ctx.lineDashOffset = 1;
      ctx.setLineDash([5, 2]);
    } else {
      ctx.setLineDash([1, 0]);
    }
    if (hover && fill) {
      ctx.fillStyle = activeColor;
      ctx.strokeStyle = activeColor;
    }
    roundRect(ctx, x, y, STYLE_CONFIG.width, STYLE_CONFIG.width);
    ctx.stroke();
    ctx.fill();
    ctx.restore();
  };

  const renderMonthLabel = (ctx: CanvasRenderingContext2D) => {
    const { monthLabel } = displayConfig;
    ctx.save();
    Object.entries(monthLabel).forEach(([month, offset]) => {
      ctx.font = "20px 'HarmonyOS'";
      ctx.fillStyle = '#333';
      ctx.textBaseline = 'middle';
      ctx.fillText(`${month}月`, 40 * offset + 40, 20);
    });
    ctx.restore();
  };

  const renderDayLabel = (ctx: CanvasRenderingContext2D) => {
    ctx.save();
    ['日', '一', '二', '三', '四', '五', '六'].forEach((i, index) => {
      ctx.font = "20px 'HarmonyOS'";
      ctx.textAlign = 'center';
      ctx.fillStyle = '#333';
      ctx.textBaseline = 'middle';
      ctx.fillText(i, 20, 40 * index + 20);
    });
    ctx.restore();
  };

  const resize = () => {
    if (!canvasRef.current || !canvasRef.current.parentNode) {
      return;
    }
    const size = 2;
    const mountDOM = canvasRef.current.parentNode as Element;
    const canvasDOM = canvasRef.current;

    const style = getComputedStyle(mountDOM);
    const width = Number(style.width.split('px')?.[0]);
    const height = Number(style.height.split('px')?.[0]);
    canvasDOM.setAttribute('width', width * size + '');
    canvasDOM.setAttribute('height', height * size + '');
    canvasDOM.style.width = width + 'px';
    canvasDOM.style.height = height + 'px';
    render();
  };

  return (
    <div className="check-calendar__container">
      <canvas ref={canvasRef} onClick={onClick}></canvas>
    </div>
  );
};

export default CanvasCheckCalendar;
